package com.lambkit.module.cms.web.interceptor;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.plugin.ehcache.CacheKit;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
 * http://www.jfinal.com/share/344
 * @author yangyong
 *
 */
public class ViewCacheInterceptor implements Interceptor {
	
	private static ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>();

	@Override
	public void intercept(Invocation inv) {
		ViewCache viewCache = inv.getMethod().getAnnotation(ViewCache.class);
		if (viewCache != null) {
			Controller c = inv.getController();
			String cacheName = c.getClass().getName();
			String cacheKey = getCacheKey(c, inv.getMethodName());
			String cacheData = CacheKit.get(cacheName, cacheKey);
			if (cacheData != null) {
				c.renderHtml(cacheData);
			} else {
				inv.invoke();
				buildCache(inv, c, cacheName, cacheKey, viewCache);
			}
			return;
		}

		inv.invoke();
	}

	private ReentrantLock getLock(String key) {
		ReentrantLock lock = lockMap.get(key);
		if (lock != null) {
			return lock;
		}
		lock = new ReentrantLock();
		ReentrantLock previousLock = lockMap.putIfAbsent(key, lock);
		return previousLock == null ? lock : previousLock;
	}

	private String getCacheKey(Controller c, String methodName) {
		StringBuilder sb = new StringBuilder(c.getClass().getName());
		String urlPara = c.getPara();
		if (urlPara != null) {
			sb.append('/').append(urlPara);
		}
		String queryString = c.getRequest().getQueryString();
		if (queryString != null) {
			sb.append('?').append(queryString);
		}
		return sb.length() > 0 ? methodName + ":" + sb.toString() : methodName;
	}

	private void buildCache(Invocation inv, Controller c, String cacheName, String cacheKey, ViewCache viewCache) {
		Lock lock = getLock(cacheName);
		lock.lock(); // prevent cache snowslide
		try {
			Object cacheData = CacheKit.get(cacheName, cacheKey);
			if (cacheData == null) {
				Map<String, Object> map = new HashMap<String, Object>();
				for (Enumeration<String> names = c.getRequest().getAttributeNames(); names.hasMoreElements();) {
					String name = names.nextElement();
					map.put(name, c.getRequest().getAttribute(name));
				}

				String view = viewCache.view();
				if (view != null && view.length() > 0 && view.charAt(0) != '/') {
					view = inv.getViewPath() + view;
				}

				putCache(cacheName, cacheKey, c.renderToString(view, map), viewCache.live(), viewCache.idle());
			}
		} finally {
			lock.unlock();
		}
	}

	private void putCache(String cacheName, Object key, Object value, int live, int idle) {
		Element element = new Element(key, value);
		element.setEternal(false);
		element.setTimeToLive(live);// 单位：秒；对象存活时间，指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0，表示一直可以访问
		element.setTimeToIdle(idle);// 单位：秒；对象空闲时间，指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0，表示一直可以访问
		getOrAddCache(cacheName).put(element);
	}

	private Cache getOrAddCache(String cacheName) {
		CacheManager cacheManager = CacheKit.getCacheManager();
		Cache cache = cacheManager.getCache(cacheName);
		if (cache == null) {
			cacheManager.addCacheIfAbsent(cacheName);
			cache = cacheManager.getCache(cacheName);
		}
		return cache;
	}
}
